/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package lexico;

/**
 *
 * @author Gabriel, Lucas, Rodrigo
 */
import org.antlr.runtime.Lexer;
import org.antlr.runtime.Token;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.RecognizerSharedState;
import programa.TabelaReservada;
import programa.SimpleToken;

@SuppressWarnings({"all", "warnings", "unchecked"})
public class AnalisadorLexico extends Lexer {

    private Token eof;
    private int currentIndex;
    public static TabelaReservada tabelaReservada;
    /* categorias de token */

    public TabelaReservada getTabelaReservada() {
        return tabelaReservada;
    }

    public int getCurrentIndex() {
        return currentIndex;
    }

    public void setCurrentIndex(int currentIndex) {
        this.currentIndex = currentIndex;
    }

    /* construtores herdados de Lexer */
    public Lexer[] getDelegates() {
        return new Lexer[]{};
    }

    public AnalisadorLexico(CharStream input) {
        this(input, new RecognizerSharedState());
        this.eof = Token.EOF_TOKEN;
        this.eof.setText("EOF");
        this.eof.setType(Token.EOF);
        this.state.token = new SimpleToken(0, "");
        this.state.token.setLine(input.getLine());
        this.currentIndex = 0;
        tabelaReservada = new TabelaReservada();
    }

    public AnalisadorLexico(CharStream input, RecognizerSharedState state) {
        super(input, state);
    }

    public SimpleToken getTokenReservado(int tokenType, String tokenText) {
        return tabelaReservada.get(tabelaReservada.indexOf(new SimpleToken(tokenType, tokenText)));
    }

    public void mark() {
        this.input.mark();
    }

    public void rewind() {
        this.input.rewind();
    }

    //lê arquivo de entrada até encontrar token EOF
    @Override
    //consome input até formar o próximo token, retornando-o; chamada dos autômatos reconhecedores
    public Token nextToken() {
        if (currentIndex < input.size()) {
            mTokens(); //método sobreescrito de Lexer, chamada dos autômatos reconhecedores
        } else {
            state.token = eof;
        }
        if (state.token.getType() == eof.getType()) {
            if (input.getLine() == 1) {
                state.token.setLine(input.getLine());
            } else {
                state.token.setLine(input.getLine() - 1);
            }
        } else {
            state.token.setLine(input.getLine());
        }
        return state.token;
    }

    //método de Lexer, precisa ser sobrescrito
    @Override
    public void mTokens() {
        state.channel = 0; //estado inicial
        if (input.index() == 0) {
            state.text = getIndexChar().toString();
        }
        if (isEspacoBranco(getIndexChar())) {
            mEspacoBranco();
        } else if (isCadeiaLiteral(getIndexChar())) {
            mCadeiaLiteral();
        } else if (isComentario(getIndexChar())) {
            mComentario();
        } else if (isNumero(getIndexChar())) {
            mNumero();
        } else if (isIdentificador(getIndexChar(), 0)) {
            mIdentificador();
        } else {
            mErro();
        }
    }

    public void mIdentificador() {
        do {
            consume(); //consome primeiro caractere, validado por mTokens()
        } while (isIdentificador(getIndexChar(), 1)); //consome caracteres válidos de identificador e palavra reservada
        if (tabelaReservada.contains(new SimpleToken(SimpleToken.PALAVRA_RESERVADA, getIndexString()))) { //verifica se cadeia é palavra reservada
            state.token = tabelaReservada.get(tabelaReservada.indexOf(new SimpleToken(SimpleToken.PALAVRA_RESERVADA, getIndexString()))); //Token -> SimpleToken.PALAVRA_RESERVADA
        } else {
            state.token = new SimpleToken(SimpleToken.IDENTIFICADOR, getIndexString()); //Token -> identificador
        }
        updateIndex(); //atualiza indice do caractere a ser lido
    }

    public void mNumero() {
        switch (state.channel) {
            case 0: //estado inicial, consome números até encontrar outro caractere ou '.'
                do {
                    consume();
                } while (isNumero(getIndexChar()));
                if (getIndexChar().equals('.') & isNumero(lookAheadChar())) { //se após '.' existir numero, numero real será formado
                    consume(); //consome '.'
                    state.channel = 1; //proximo estado, numero real
                    mNumero();
                } else { //caso contrário, numero inteiro é formado
                    state.token = new SimpleToken(SimpleToken.NUMERO_INTEIRO, getIndexString()); //Token -> numero_inteiro
                    updateIndex();
                }
                break;
            case 1: // '.' já consumido, consome numeros até encontrar outro caractere formando numero real
                do {
                    consume();
                } while (isNumero(getIndexChar()));
                state.token = new SimpleToken(SimpleToken.NUMERO_REAL, getIndexString()); //Token -> numero_real
                updateIndex();
        }
    }

    public void mCadeiaLiteral() {
        switch (state.channel) {
            case 0:
                input.mark(); //volta input para este ponto em caso de erro (cadeia literal mal fechada, quebra de linha ou EOF)
                do {
                    consume();
                    if (getIndexChar().equals('\n') | getIndexChar().equals('"')) {
                        break;
                    }
                } while (!state.text.equals(eof.getText())); //condição de parada -> EOF
                if (getIndexChar().equals('"')) {
                    //cadeia literal bem formada!
                    consume(); //consome '"'
                    state.token = new SimpleToken(SimpleToken.CADEIA_LITERAL, getIndexString()); //Token -> cadeia_literal
                    updateIndex();
                } else { //erro: EOF, cadeia mal fechada ou quebra de linha
                    input.rewind(); //rollback nos consumes
                    state.text = getIndexChar().toString();
                    mErro(); // " - simbolo nao identificado
                }
        }
    }

    public void mComentario() {
        switch (state.channel) {
            case 0:
                //input.mark();
                do {
                    consume();
                    if (getIndexChar().equals('\n') | getIndexChar().equals('}')) {
                        break;
                    }
                } while (!state.text.equals(eof.getText()));
                if (getIndexChar().equals('}')) { //comentario bem formado!
                    consume(); //consome '}'
                    state.token = new SimpleToken(SimpleToken.COMENTARIO, getIndexString()); //Token -> comentario
                    updateIndex();
                } else if (getIndexChar().equals('\n')) { //erro: comentario mal formado
                    consume();
                    //input.rewind();
                    mErro2(); //consome até encontrar token EOF
                } else {
                    mErro2();
                }
        }
    }

    public void mEspacoBranco() { //caracteres do tipo: \n \t \r " "
        if (lookAheadChar().equals('\0')) {
            state.token = new SimpleToken(eof);
        } else {
            state.token = new SimpleToken(SimpleToken.ESPACO_BRANCO, state.text);
        }
        consume();
        updateIndex();
    }

    //tratamento de erros (simbolo nao identificado) e detecção de símbolos reservados
    public void mErro() {
        switch (state.channel) {
            case 0: //estado inicial, verificação do caractere (simbolo reservado, EOF ou simbolo nao identificado)
                switch (getIndexChar()) {
                    case '<': //simbolo reservado '<' e detecção de símbolos reservados compostos por mais de um caractere ('<-', '<>', '<=')
                        switch (lookAheadChar()) {
                            case '-':
                            case '>':
                            case '=':
                                state.text = getIndexChar().toString() + lookAheadChar().toString();
                                if (tabelaReservada.contains(new SimpleToken(SimpleToken.SIMBOLO_RESERVADO, state.text))) {
                                    state.channel = 2;
                                    mErro();
                                } else {
                                    state.text = getIndexChar().toString();
                                    state.channel = 3; //estado de erro, simbolo não existe na tabela reservada
                                    mErro();
                                }
                                break;
                            default:
                                state.text = getIndexChar().toString();
                                state.channel = 1; //validação do simbolo '<'
                                mErro();
                        }
                        break;
                    case '>': //simbolo reservado '>' e detecção de símbolo reservado composto por mais de um caractere ('>=')
                        if (lookAheadChar().equals('=')) {
                            state.text = getIndexChar().toString() + lookAheadChar().toString();
                            if (tabelaReservada.contains(new SimpleToken(SimpleToken.SIMBOLO_RESERVADO, state.text))) {
                                state.channel = 2;
                                mErro();
                            } else {
                                state.text = getIndexChar().toString();
                                state.channel = 3; //estado de erro, simbolo não existe na tabela reservada
                                mErro();
                            }
                        } else {
                            state.text = getIndexChar().toString();
                            state.channel = 1; //validação do simbolo '>'
                            mErro();
                        }
                        break;
                    case '.': //simbolo reservado '.' e detecção de símbolo reservado composto por mais de um caractere ('.')
                        if (lookAheadChar().equals('.')) {
                            state.text = getIndexChar().toString() + lookAheadChar().toString();
                            if (tabelaReservada.contains(new SimpleToken(SimpleToken.SIMBOLO_RESERVADO, state.text))) {
                                state.channel = 2;
                                mErro();
                            } else {
                                state.text = getIndexChar().toString();
                                state.channel = 3; //estado de erro, simbolo não existe na tabela reservada
                                mErro();
                            }
                        } else {
                            state.text = getIndexChar().toString();
                            state.channel = 1; //validação do simbolo '.'
                            mErro();
                        }
                        break;
                    case '\0':
                        state.channel = 3;
                        mErro();
                        break;
                    default: //detecção de outros simbolos reservados, se não for retorna token de ERRO (simbolo nao identificado)

                        state.text = getIndexChar().toString();

                        if (tabelaReservada.contains(new SimpleToken(SimpleToken.SIMBOLO_RESERVADO, state.text))) {
                            state.channel = 1;
                            mErro();
                        } else {
                            state.channel = 3;
                            mErro();
                        }
                }
                break;
            case 1: //estado válido, simbolo reservado
                state.text = getIndexChar().toString();
                state.token = tabelaReservada.get(tabelaReservada.indexOf(new SimpleToken(SimpleToken.SIMBOLO_RESERVADO, state.text)));
                consume(); //consome caracter(es) que formam simbolo reservado
                updateIndex();
                break;
            case 2:
                state.token = tabelaReservada.get(tabelaReservada.indexOf(new SimpleToken(SimpleToken.SIMBOLO_RESERVADO, state.text)));
                for (int i = 0; i < state.token.getText().length(); i++) {
                    consume(); //consome caracter(es) que formam simbolo reservado
                }
                updateIndex();
                break;
            case 3: //estado erro
                if (state.text.equals(eof.getText())) {
                    state.token = new SimpleToken(eof);
                } else {
                    state.token = new SimpleToken(SimpleToken.ERRO, state.text);
                    consume();
                    updateIndex();
                }
        }
    }

    //erro de comentario mal formado
    public void mErro2() {
        state.token = new SimpleToken(SimpleToken.ERRO2, state.text);
    }

    //retorna o caracter do índice do input
    public Character getIndexChar() {
        Character c;
        if (input.index() < input.size()) {
            c = input.substring(input.index(), input.index()).charAt(0);
            return c;
        } else {
            state.text = eof.getText();
            c = '\0';
            return c;
        }
    }

    //retorna o valor do próximo caracter a ser lido após um consume
    public Character lookAheadChar() {
        Character c;
        if (input.index() + 1 < input.size()) {
            try {
                c = input.substring(input.index() + 1, input.index() + 1).charAt(0);
            } catch (Exception e) {
                state.text = eof.getText();
                c = '\0';
            }
            return c;
        } else {
            state.text = eof.getText();
            c = '\0';
            return c;
        }
    }

    //retorna cadeia de caracteres (primeiro lido pelo automato até o último caracter válido)
    public String getIndexString() {
        if (input.index() <= input.size()) {
            return input.substring(currentIndex, input.index() - 1);
        } else {
            return eof.getText();
        }
    }

    //consome caracter e recebe valor de novo caracter
    public void consume() {
        input.consume();
        if (input.index() < input.size()) {
            state.text = getIndexChar().toString();
        } else {
            state.text = eof.getText();
        }
    }

    //atualiza index de controle
    public void updateIndex() {
        currentIndex = input.index();
    }

    public boolean isCadeiaLiteral(Character c) {
        if (c.equals('"')) {
            return true;
        } else {
            return false;
        }
    }

    public boolean isComentario(Character c) {
        if (c.equals('{')) {
            return true;
        } else {
            return false;
        }
    }

    public static boolean isEspacoBranco(Character c) {
        if (Character.isWhitespace(c)) {
            return true;
        } else {
            return false;
        }
    }

    public static boolean isNumero(Character c) {
        if (Character.isDigit(c)) {
            return true;
        } else {
            return false;
        }
    }

    public static boolean isIdentificador(Character c, int estado) {
        if (estado == 0) {
            if (Character.isLetter(c) | c.equals('_')) {
                return true;
            } else {
                return false;
            }
        } else {
            if (Character.isLetter(c) | Character.isDigit(c) | c.equals('_')) {
                return true;
            } else {
                return false;
            }
        }
    }
}